#coding=UTF-8
"""
ковертирует HTML entities в юникодные символы
использование:
decode(u'Всегда говори &laquo;ДА&raquo;')
    
"""

import htmlentitydefs
from htmlentitydefs import name2codepoint as n2cp
import re
import cStringIO

from html5lib import treebuilders, treewalkers, serializer, sanitizer, HTMLParser

from xml.etree import ElementTree

from BeautifulSoup import BeautifulSoup

# matches a character entity reference (decimal numeric, hexadecimal numeric, or named).
charrefpat = re.compile(r'&(#(\d+|x[\da-fA-F]+)|[\w.:-]+);?')
def htmlspecialchars(text):
    """
        Decode HTML entities in the given.
        text should be a unicode string, as that is what we insert.
       
    """
   
    if type(text) is unicode:
        uchr = unichr
    else:
        uchr = lambda value: value > 255 and unichr(value) or chr(value)

    def entitydecode(match, uchr=uchr):
        entity = match.group(1)
        if entity.startswith('#x'):
            return uchr(int(entity[2:], 16))
        elif entity.startswith('#'):
            return uchr(int(entity[1:]))
        elif entity in name2codepoint:
            return uchr(n2cp[entity])
        else:
            return match.group(0)
    return charrefpat.sub(entitydecode, text)

def substitute_entity(match):
    ent = match.group(2)
    if match.group(1) == "#":
        if ent == '160': #обработка &nbsp;
            return ' '
        return unichr(int(ent))
    else:
        if ent == 'nbsp': #обработка &nbsp;
            return ' '
        
        cp = n2cp.get(ent)

        if cp:
            return unichr(cp)
        else:
            return match.group()

def encode(data, is_utf = False):
    """
    заменяет разные левые символы в юникодной строке в HTML entities
    is_utf - если выход и выход представлены в UTF8
    """
    if data:
        if is_utf:
            data = data.decode('UTF8')
        from UserString import MutableString
        rez = MutableString()
        for i in data:
            if ord(i) in htmlentitydefs.codepoint2name:
                rez += '&%s;' % htmlentitydefs.codepoint2name[ord(i)]
            else:
                rez += i
            
        if is_utf:
            return rez.encode('UTF8')
        return rez
    else:
        return ''


def decode(data, adjust_quotes = True):
    """
    ковертирует HTML entities в юникодные символы
    data - юникодная строка
    adjust_quotes - если True (default) - переводит кавычки вида «» в ""
    возвращает юникодную строку
    
    """
    

    entity_re = re.compile("&(#?)(\d{1,5}|\w{1,8});")
    s = entity_re.subn(substitute_entity, data)[0]
    
    
    if adjust_quotes:
        return s.replace(u'«', '"').replace(u'»', '"')
    else:
        return s


def sanitize(data, is_utf = False, allowed_elements = ('b', 'a', 'i', 'p', 'u', 'br'), allowed_attributes = ('href',)):
    
    if is_utf:
        encoding = 'UTF-8'
    else:
        encoding = None

    def sanitizer_factory(*args, **kwargs):
        san = sanitizer.HTMLSanitizer(*args, **kwargs)
        san.allowed_elements = allowed_elements
        san.allowed_attributes = allowed_attributes
        san.strip_tokens = True
        san.lowercaseElementName = True
        san.lowercaseAttrName = True
        return san

    def sanitize_(data, encoding=None):
        parser = HTMLParser(tree=treebuilders.getTreeBuilder("dom"),
            tokenizer=sanitizer_factory)
        walker = treewalkers.getTreeWalker("dom")
        stream = walker(parser.parseFragment(data, encoding=encoding))
        slzr = serializer.htmlserializer.HTMLSerializer(omit_optional_tags=False,
            quote_attr_values=True, use_trailing_solidus=True)
        return slzr.render(stream, encoding)
        
    return sanitize_(data, encoding)
    
def sanitize_with_strip(data, is_utf = False, allowed_elements = ('b', 'a', 'i', 'p', 'u', 'br'), allowed_attributes = ('href',)):
    """
    коцает из html все неразрешенные элементы
    НЕ ПРОВЕРЕНА НА БЕЗОПАСНОСТЬ. ПОКА ТОЛЬКО ДЛЯ АДМИНКИ
    """
    
    if is_utf:
        data = data.decode('UTF-8')
    
    soup = BeautifulSoup(data)
    for tag in soup.findAll(True):
        if tag.name not in allowed_elements:
            tag.hidden = True
        tag.attrs = [(attr, val) for attr, val in tag.attrs if attr in allowed_attributes]
    rez =  soup.renderContents().decode('UTF-8')
    
    if is_utf:
        return rez.encode('UTF-8')


def make_plain(data, is_utf = False):
    
    if is_utf:
        data = data.decode('UTF8')
            
    parser = HTMLParser(tree = treebuilders.getTreeBuilder('etree', ElementTree))
        
    tree = parser.parse(data)
        
    #TODO: сделать фикс для разной cdata (jscript, комменты и прочее)
    text_arr = []
    for node in tree.getiterator():
        if node.text:
            text_arr.append(node.text)
            
    rez = sanitize(' '.join(text_arr), False, allowed_elements = [], allowed_attributes = [])
    
    if is_utf:
        return rez.encode('UTF8')
    else:
        return rez
    
    
if __name__ == "__main__":
    
    assert decode(u'Всегда&nbsp;говори &laquo;ДА&raquo;', adjust_quotes = False) == u"Всегда говори «ДА»"
    assert decode(u'Всегда говори &laquo;ДА&raquo;') == u'Всегда говори "ДА"'
    print make_plain('<mama> d <wah ram> a')
    
    
    
    